package jmine.tec.utils.reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import jmine.tec.utils.Tuple;
import jmine.tec.utils.UnmodifiableTuple;

/**
 * Classe que auxilia o uso de annotations
 * 
 * @author takeshi.fukushima
 */
public final class AnnotationUtils {

    /**
     */
    private AnnotationUtils() {
    }

    /**
     * Procura a classe que é superclasse da classe passada que possui a anotacao dada. Devolve <code>null</code> caso nenhuma seja
     * encontrada.
     * 
     * @param baseType o tipo base
     * @param annotation a anotacao
     * @return {@link Class}
     */
    public static Class<?> findClassWithAnnotation(Class<?> baseType, Class<? extends Annotation> annotation) {
        if (baseType == null || baseType == Object.class) {
            return null;
        }
        if (baseType.isAnnotationPresent(annotation)) {
            return baseType;
        }
        return findClassWithAnnotation(baseType.getSuperclass(), annotation);
    }

    /**
     * Procura a classe que é superclasse da classe passada que possui a anotacao dada. Devolve <code>null</code> caso nenhuma seja
     * encontrada.
     * 
     * @param baseType o tipo base
     * @param annotation a anotacao
     * @param <E> o tipo da anotacao
     * @return {@link Class}
     */
    public static <E extends Annotation> Tuple<Class<?>, E> findClassAndAnnotationWithAnnotation(Class<?> baseType, Class<E> annotation) {
        if (baseType == null || baseType == Object.class) {
            return null;
        }
        E value = baseType.getAnnotation(annotation);
        if (value != null) {
            return new UnmodifiableTuple<Class<?>, E>(baseType, value);
        }
        return findClassAndAnnotationWithAnnotation(baseType.getSuperclass(), annotation);
    }

    /**
     * Procura a classe que é superclasse OU INTERFACE da classe passada que possui a anotacao dada. Devolve <code>null</code> caso nenhuma
     * seja encontrada.
     * 
     * @param baseType o tipo base
     * @param annotation a anotacao
     * @param <E> o tipo da anotacao
     * @return {@link Class}
     */
    public static <E extends Annotation> Tuple<Class<?>, E> findTypeAndAnnotation(Class<?> baseType, Class<E> annotation) {
        if (baseType == null || baseType == Object.class) {
            return null;
        }
        E value = baseType.getAnnotation(annotation);
        if (value != null) {
            return new UnmodifiableTuple<Class<?>, E>(baseType, value);
        }
        Class<?>[] interfaces = baseType.getInterfaces();
        for (Class<?> interf : interfaces) {
            Tuple<Class<?>, E> tuple = findTypeAndAnnotation(interf, annotation);
            if (tuple != null) {
                return tuple;
            }
        }
        return findTypeAndAnnotation(baseType.getSuperclass(), annotation);
    }

    /**
     * Procura todos os metodos com as anotacoes passadas.
     * 
     * @param <E> o tipo da anotacao
     * @param declaringClass a classe que declara os metodos
     * @param annotation o tipo da anotacao
     * @return {@link Collection} de {@link Method}
     */
    public static <E extends Annotation> Collection<Tuple<Method, E>> findMethodsWithAnnotation(Class<?> declaringClass, Class<E> annotation) {
        List<Tuple<Method, E>> list = new ArrayList<Tuple<Method, E>>();
        for (Method method : declaringClass.getMethods()) {
            E value = method.getAnnotation(annotation);
            if (value != null) {
                list.add(UnmodifiableTuple.newTuple(method, value));
            }
        }
        return list;
    }

    /**
     * Procura todos os metodos com as anotacoes passadas.
     * 
     * @param <E> o tipo da anotacao
     * @param declaringClass a classe que declara os metodos
     * @param annotation o tipo da anotacao
     * @return {@link Collection} de {@link Method}
     */
    public static <E extends Annotation> Collection<Tuple<Method, E>> findDeclaredMethodsWithAnnotation(Class<?> declaringClass,
            Class<E> annotation) {
        List<Tuple<Method, E>> list = new ArrayList<Tuple<Method, E>>();
        for (Method method : declaringClass.getDeclaredMethods()) {
            E value = method.getAnnotation(annotation);
            if (value != null) {
                list.add(UnmodifiableTuple.newTuple(method, value));
            }
        }
        return list;
    }

}
